﻿using System;

namespace QSharp.Scheme.Utility.HBSpaceMgmt
{
    public class Section
    {
        public IStream TargetStream { get { return mTargetStream; } }
        /**
         *  <summary>
         *   staring point and total size of the target stream
         *  </summary>
         */
        public ISection Inferior { get { return mInferior; } }
        public IPosition TargetStart { get { return mTargetStart; } }
        public ISize TargetSize { get { return mTargetSize; } }
        public IOperator TargetOperator { get { return mTargetOperator; } }
        public IPaginator TargetPaginator { get { return mTargetPaginator; } }
        public ISize TargetPageSize { get { return mTargetPageSize; } }

        protected ISection mInferior = null;    /* inferior section that is governed by this */
        protected IStream mTargetStream = null;
        protected IPosition mTargetStart = null;
        protected ISize mTargetSize = null;
        protected IOperator mTargetOperator = null;
        protected IPaginator mTargetPaginator = null;
        protected ISize mTargetPageSize = null;

        public IChunkDescriptorEncoder HoleHeaderEncoder = null;
        public IChunkDescriptorEncoder HoleFooterEncoder = null;
        public IChunkDescriptorEncoder LumpHeaderEncoder = null;
        public IChunkDescriptorEncoder LumpFooterEncoder = null;

        /**
         * <remarks>
         *  `targetStart' and `targetSize' are paginated variables
         * </remarks>
         */
        public void SetTarget(ISection inferior, IStream targetStream, IPosition targetStart, 
            ISize targetSize, IOperator targetOperator, IPaginator targetPaginator, ISize targetPageSize)
        {
            mInferior = inferior;
            mTargetStream = targetStream;
            mTargetStart = targetStart;
            mTargetSize = targetSize;
            mTargetOperator = targetOperator;
            mTargetPaginator = targetPaginator;
            mTargetPageSize = targetPageSize;

            if (inferior is BTreeSection)
            {
                BTreeSection bts = (BTreeSection)inferior;
                    bts.Superior = this as ISection;
            }
        }


        /**
         * <remarks>
         *  `size' is a paginated value that represents a space that
         *  may accoommodate multiple nodes, this method would allocate
         *  such a space in request and segment it into pages each can
         *  accommodate exactly one node
         * </remarks>
         */
        protected void AllocatePages(Hole hole, ISize size, out Hole newHole)
        {
            IPosition holeStart = hole.Start;
            ISize holeSize = hole.Size;

            int cmpHole = holeSize.CompareTo(size);
            if (cmpHole < 0)
            {
                throw new ApplicationException("Bad section data");
            }

            ISize onePage = TargetPaginator.OnePage;    // paginated size for one page
            IPosition allocPos = holeStart;
            IPosition endPos;
            ISize remSize = size;
            while (remSize.CompareTo(onePage) >= 0)
            {

                /* Encode the header for the newly allocated page */
                
                TargetStream.Position = TargetPaginator.Unpaginate(allocPos, TargetPageSize);

                LumpHeaderEncoder.ChunkSize = onePage;
                LumpHeaderEncoder.Encode(TargetStream);

                /* Encode the footer for the newly allocated page */
                LumpFooterEncoder.ChunkSize = onePage;
                endPos = TargetPaginator.Add(allocPos, onePage);
                endPos = TargetPaginator.Unpaginate(endPos, TargetPageSize);
                TargetStream.Position = TargetOperator.Subtract(endPos, LumpFooterEncoder.EncodedSize);
                LumpFooterEncoder.Encode(TargetStream);

                remSize = TargetPaginator.Subtract(remSize, onePage);
                allocPos = TargetOperator.Add(allocPos, onePage);
            }

            if (cmpHole > 0)
            {
                /* update hole */

                /* shrink the hole and add it to the management system */
                /* <remarks>
                 *  The following assignment doesn't make any change to `hole'
                 * </remarks>
                 */
                holeStart = TargetPaginator.Add(holeStart, size);
                holeSize = TargetPaginator.Subtract(holeSize, size);

                newHole = new Hole(holeStart, holeSize);

                /* Encode the header for the shrunken hole */
                TargetStream.Position = TargetPaginator.Unpaginate(holeStart, TargetPageSize);
                HoleHeaderEncoder.ChunkSize = holeSize;
                HoleHeaderEncoder.Encode(TargetStream);

                /* Encode the tail for the shrunken hole */
                endPos = TargetPaginator.Add(holeStart, holeSize);
                endPos = TargetPaginator.Unpaginate(endPos, TargetPageSize);
                TargetStream.Position = TargetOperator.Subtract(endPos, HoleFooterEncoder.EncodedSize);
                HoleFooterEncoder.Encode(TargetStream);
            }
            else
            {
                newHole = null;
            }
        }

        /**
         * <summary>
         *  Allocate for the target section
         * </summary>
         * <remarks>
         *  If the hole is shrunk but not eliminated, 
         *  `newHoleStart' and `newHoleSize' return the 
         *  starting position and size of the new 
         *  hole respectively;
         *  `hole' is not changed in this method
         *  
         *  `hole' and `size' are paginated variables
         *  
         * </remarks>
         */
        protected void Allocate(Hole hole, ISize size, out Hole newHole)
        {
            IPosition holeStart = hole.Start;
            ISize holeSize = hole.Size;

            int cmpHole = holeSize.CompareTo(size);
            if (cmpHole < 0)
            {
                throw new ApplicationException("Bad section data");
            }

            /* Encode the header for the newly allocated chunk */
            IPosition allocPos = holeStart;
            TargetStream.Position = TargetPaginator.Unpaginate(allocPos, TargetPageSize);
            LumpHeaderEncoder.ChunkSize = size;
            LumpHeaderEncoder.Encode(TargetStream);

            /* Encode the footer for the newly allocated chunk */
            LumpFooterEncoder.ChunkSize = size;
            IPosition endPos = TargetPaginator.Add(allocPos, size);
            endPos = TargetPaginator.Unpaginate(endPos, TargetPageSize);
            TargetStream.Position = TargetOperator.Subtract(endPos, LumpFooterEncoder.EncodedSize);
            LumpFooterEncoder.Encode(TargetStream);

            if (cmpHole > 0)
            {
                /* update hole */

                /* shrink the hole and add it to the management system */
                /* <remarks>
                 *  The following assignment doesn't make any change to `hole'
                 * </remarks>
                 */
                holeStart = TargetPaginator.Add(holeStart, size);
                holeSize = TargetPaginator.Subtract(holeSize, size);

                newHole = new Hole(holeStart, holeSize);

                /* Encode the header for the shrunken hole */
                TargetStream.Position = TargetPaginator.Unpaginate(holeStart, TargetPageSize);
                HoleHeaderEncoder.ChunkSize = holeSize;
                HoleHeaderEncoder.Encode(TargetStream);

                /* Encode the tail for the shrunken hole */
                endPos = TargetPaginator.Add(holeStart, holeSize);
                endPos = TargetPaginator.Unpaginate(endPos, TargetPageSize);
                TargetStream.Position = TargetOperator.Subtract(endPos, HoleFooterEncoder.EncodedSize);
                HoleFooterEncoder.Encode(TargetStream);
            }
            else
            {
                newHole = null;
            }
        }

        /**
         * <remarks>
         *  `destPos' is paginated position
         * </remarks>
         */
        protected void Deallocate(IPosition pos, out Hole oldHole1, 
            out Hole oldHole2, out Hole newHole)
        {
            TargetStream.Position = TargetPaginator.Unpaginate(pos, TargetPageSize);

            /* check the header */
            bool bDecoded = LumpHeaderEncoder.Decode(TargetStream);
            if (!bDecoded)
                throw new ApplicationException("Deallocating at invalid position");
            ISize size = LumpHeaderEncoder.ChunkSize;   /* paginated */

            IPosition newHolePos = pos;
            ISize newHoleSize = size;

            /* check the tail */
            IPosition tmpPos = TargetPaginator.Add(pos, size);
            tmpPos = TargetPaginator.Unpaginate(tmpPos, TargetPageSize);
            TargetStream.Position = TargetOperator.Subtract(tmpPos, LumpFooterEncoder.EncodedSize);
            bDecoded = LumpFooterEncoder.Decode(TargetStream);
            if (!bDecoded)
                throw new ApplicationException("Bad chunk");

            /* check integrity */
            ISize sizeFromTail = LumpFooterEncoder.ChunkSize;
            if (size.CompareTo(sizeFromTail) != 0)
                throw new ApplicationException("Bad chunk");

            oldHole1 = null;
            oldHole2 = null;
            /* try to access preceding chunk as free chunk */
            tmpPos = pos;
            if (tmpPos.CompareTo(TargetStart) > 0)
            {
                tmpPos = TargetPaginator.Unpaginate(tmpPos, TargetPageSize);
                TargetStream.Position = TargetOperator.Subtract(tmpPos, HoleFooterEncoder.EncodedSize);
                bDecoded = HoleFooterEncoder.Decode(TargetStream);
                if (bDecoded)
                {   // the preceding chunk is a free chunk
                    ISize tmpSize = HoleFooterEncoder.ChunkSize; /* paginated */

                    newHolePos = TargetPaginator.Subtract(pos, tmpSize);
                    newHoleSize = TargetPaginator.Add(newHoleSize, tmpSize);

                    oldHole1 = new Hole(newHolePos, tmpSize);
                }
            }

            /* try to access succeeding chunk as free chunk */
            tmpPos = TargetPaginator.Add(pos, size);
            if (tmpPos.CompareTo(TargetStart.Add(TargetSize)) < 0)
            {
                TargetStream.Position = TargetPaginator.Unpaginate(tmpPos, TargetPageSize);
                bDecoded = HoleHeaderEncoder.Decode(TargetStream);
                if (bDecoded)
                {   // the succeeding chunk is a free chunk
                    ISize tmpSize = HoleHeaderEncoder.ChunkSize; /* paginated */
                    newHoleSize = TargetPaginator.Add(newHoleSize, tmpSize);

                    oldHole2 = new Hole(tmpPos, tmpSize);
                }
            }


            /**
             * <remarks>
             *  Obsolete descriptors are not explicitly removed,
             *  it doesn't matter, since nothing entirely depends 
             *  on the information extracted from stream to 
             *  guarantee the integrity of management system
             *  
             * </remarks>
             */
            /* update the hole */
            TargetStream.Position = TargetPaginator.Unpaginate(newHolePos, TargetPageSize);
            HoleHeaderEncoder.ChunkSize = newHoleSize;
            HoleHeaderEncoder.Encode(TargetStream);

            tmpPos = TargetPaginator.Add(newHolePos, newHoleSize);
            tmpPos = TargetPaginator.Unpaginate(tmpPos, TargetPageSize);
            TargetStream.Position = TargetOperator.Subtract(tmpPos, HoleFooterEncoder.EncodedSize);
            HoleFooterEncoder.Encode(TargetStream);

            newHole = new Hole(newHolePos, newHoleSize);
        }
    }
}
